#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "benc.h"
#include "metainfo.h"
#include "tools.h"


int parse_metainfo(char *buffer, int buff_len, xx_metainfo *meta)
{
	int				  ret   = 0;
	benc			  dict  = {0};		// how to init a struct
	benc			**elems = NULL;

	if ( NULL == buffer || NULL == meta) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	xx_inf("loading meta....");

	ret = benc_metafile(buffer, buff_len, &dict, meta->hash_str);
	if ( ret == -1 ) {
		return -1;
	}
	elems = dict.b_elems;

	xx_inf("benc_dict end");
	while(*elems){
		if ( BENC_STRING != (*elems)->type ){
			xx_err("fuck you...\n");
			return -1;
		}

//		xx_dbg("str: %s\n", (*elems)->string);
		if ( !strncmp((*elems)->string, "announce-list", 13) ) {	// a list of lists of strings
			elems++;
			if ( (*elems)->type != BENC_LIST) {
				return -1;
			}
			if ( -1 == parse_announce_list(*elems, meta) ) {
				return -1;
			}
		}
		else if ( !strncmp((*elems)->string, "announce", 8) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}

			meta->announce = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->announce, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "creation date", 13) ) {
			elems++;
			if ( (*elems)->type != BENC_INTEGER) {
				return -1;
			}
			meta->creat_date = (*elems)->integer;
		}
		else if ( !strncmp((*elems)->string, "comment", 7) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->comment = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->comment, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "created by", 10) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->creat_by = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->creat_by, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "encoding", 8) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->encoding = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->encoding, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "info", 4) ) { // todo
			elems++;
			if ( (*elems)->type != BENC_DICT) {
				return -1;
			}
			if ( -1 == parse_file_info(*elems, meta) ) {
				return -1;
			}
		}
		else{	// todo: unknown keys				
			elems++;
		}

		elems++;
	}

	return free_dict(&dict);
}

int parse_file_info(benc *dict, xx_metainfo *meta)
{
	benc			**elems = NULL;

	if ( NULL == dict || NULL == meta) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( dict->type != BENC_DICT ) {
		return -1;
	}

	meta->info_mode = 0;		// default we take it as single file

	elems = dict->b_elems;

	while(*elems){
		if ( BENC_STRING != (*elems)->type ){
			xx_err("fuck you\n");
			return -1;
		}

//		xx_dbg("--------str:   %s\n", (*elems)->string);
		if ( !strncmp((*elems)->string, "piece length", 12) ) {
			elems++;
			if ( (*elems)->type != BENC_INTEGER) {
				return -1;
			}

			meta->piece_len = (*elems)->integer;
		}
		else if ( !strncmp((*elems)->string, "pieces", 6) ) {	// todo
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->pieces = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->pieces, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "private", 7) ) {
			elems++;
			if ( (*elems)->type != BENC_INTEGER) {
				return -1;
			}
			meta->privat = (*elems)->integer;
		}
		else if ( !strncmp((*elems)->string, "name.utf-8", 10) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->name_utf8 = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->name_utf8, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "name", 4) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->name = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->name, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "publisher-url.utf-8", 19) ) {		// note: shoud parse "aaa" befor parseing "aa"
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->publ_url_utf8 = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->publ_url_utf8, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "publisher.utf-8", 15) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->publ_utf8 = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->publ_utf8, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "publisher-url", 13) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->publ_url = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->publ_url, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "publisher", 9) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			meta->publ = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->publ, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "length", 6) ) {
			elems++;
			if ( (*elems)->type != BENC_INTEGER) {
				return -1;
			}
			if ( NULL == meta->files ) {
				meta->files = (xx_file *)malloc(sizeof(xx_file));
				if ( NULL == meta->files ) {
					xx_err("malloc failed\n");
					return -1;
				}
				memset(meta->files, 0, sizeof(xx_file));
			}
			meta->files_num = 1;
			meta->files->length = (*elems)->integer;
		}
		else if ( !strncmp((*elems)->string, "md5sum", 6) ) {
			elems++;
			if ( (*elems)->type != BENC_STRING) {
				return -1;
			}
			if ( NULL == meta->files ) {
				meta->files = (xx_file *)malloc(sizeof(xx_file));
				if ( NULL == meta->files ) {
					xx_err("malloc failed\n");
					return -1;
				}
				memset(meta->files, 0, sizeof(xx_file));
			}
			meta->files->md5sum = (char *)malloc( strlen((*elems)->string) + 1);
			memcpy(meta->files->md5sum, (*elems)->string, strlen((*elems)->string) + 1);
		}
		else if ( !strncmp((*elems)->string, "files", 5) ) {	// a list of dicts
			elems++;
			if ( (*elems)->type != BENC_LIST) {
				return -1;
			}
			meta->info_mode = 1;
			if ( -1 == parse_file_list(*elems, meta) ) {
				return -1;
			}
		}
		else{		// todo: unknown keys
			xx_dbg("=====???\n");
			elems++;
		}

		elems++;
	}

	return 0;
}

// parse file in a list of dicts
int parse_file_list(benc *list, xx_metainfo *meta)
{
	benc			**plist	   = NULL;
	benc			**elems	   = NULL;
	benc			**str_list = NULL;
	int				  i		   = 0;
	int				  j		   = 0;

	if ( NULL == list || NULL == meta) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	if ( list->type != BENC_LIST ) {
		return -1;
	}

	plist = list->b_elems;
	meta->files = NULL;

	while(*plist){		// list
		if ( BENC_DICT != (*plist)->type ){
			return -1;
		}
		elems  = (*plist)->b_elems;

		while(*elems){	// dict
			if ( BENC_STRING != (*elems)->type ){
				xx_err("fuck you\n");
				return -1;
			}

//			xx_dbg("str: %s\n", (*elems)->string);

			if ( NULL == meta->files ) {
				meta->files = (xx_file *)malloc(sizeof(xx_file) * MAX_FILE_NUM);
				memset(meta->files, 0, sizeof(xx_file) * MAX_FILE_NUM);
			}
			if ( i > 0 && 0 == (i % MAX_FILE_NUM)) {	// todo: too many files
				meta->files = (xx_file *)realloc(meta->files, sizeof(xx_file) * MAX_FILE_NUM * (i / MAX_FILE_NUM));
			}

			if ( !strncmp((*elems)->string, "md5sum", 6) ) {
				elems++;
				if ( (*elems)->type != BENC_STRING) {
					return -1;
				}

				meta->files[i].md5sum = (char *)malloc( strlen((*elems)->string) + 1);
				memcpy(meta->files[i].md5sum, (*elems)->string, strlen((*elems)->string) + 1);

			}
			else if ( !strncmp((*elems)->string, "length", 5) ) {
				elems++;
				if ( (*elems)->type != BENC_INTEGER) {
					return -1;
				}
				meta->files[i].length = (*elems)->integer;
			}
			else if ( !strncmp((*elems)->string, "path.utf-8", 10) ) {	
				elems++;
				if ( (*elems)->type != BENC_LIST) {
					return -1;
				}

				str_list = (*elems)->b_elems;
				j = 0;
				while ( *str_list ){
					if ( BENC_STRING != (*str_list)->type ) {
						return -1;
					}
					if ( NULL == meta->files[i].path_utf8 ) {
						meta->files[i].path_utf8 = (char **)malloc( sizeof(char *) * MAX_PATH_NUM);
					}
					memset(meta->files[i].path, 0, sizeof(char *) * MAX_PATH_NUM);
					if ( j > 0 && 0 == (j % MAX_PATH_NUM)) {	// todo: too many files
						meta->files[i].path_utf8 = (char **)realloc(meta->files[i].path, sizeof(char *) * MAX_FILE_NUM * (i / MAX_PATH_NUM));
					}

					meta->files[i].path_utf8[j] = (char *)malloc( strlen((*str_list)->string) + 1); 
					memcpy(meta->files[i].path_utf8[j], (*str_list)->string, strlen((*str_list)->string) + 1);

					j++;
					str_list++;
				}
			}
			else if ( !strncmp((*elems)->string, "path", 4) ) {	// a list of strings	
				elems++;
				if ( (*elems)->type != BENC_LIST) {
					return -1;
				}

				str_list = (*elems)->b_elems;
				j = 0;
				while ( *str_list ){
					if ( BENC_STRING != (*str_list)->type ) {
						return -1;
					}
					if ( NULL == meta->files[i].path ) {
						meta->files[i].path = (char **)malloc( sizeof(char *) * MAX_PATH_NUM);
					}
					memset(meta->files[i].path, 0, sizeof(char *) * MAX_PATH_NUM);
					if ( j > 0 && 0 == (j % MAX_PATH_NUM)) {	// todo: too many files
						meta->files[i].path = (char **)realloc(meta->files[i].path, sizeof(char *) * MAX_FILE_NUM * (i / MAX_PATH_NUM));
					}

					meta->files[i].path[j] = (char *)malloc( strlen((*str_list)->string) + 1); 
					memcpy(meta->files[i].path[j], (*str_list)->string, strlen((*str_list)->string) + 1);

					j++;
					str_list++;
				}
			}
			else if ( !strncmp((*elems)->string, "ed2k", 4) ) {
				elems++;
				if ( (*elems)->type != BENC_STRING) {
					return -1;
				}

				meta->files[i].ed2k = (char *)malloc( strlen((*elems)->string) + 1);
				memcpy(meta->files[i].ed2k, (*elems)->string, strlen((*elems)->string) + 1);

			}
			else if ( !strncmp((*elems)->string, "filehash", 8) ) {
				elems++;
				if ( (*elems)->type != BENC_STRING) {
					return -1;
				}

//				meta->files[i].md5sum = (char *)malloc( strlen((*elems)->string) + 1);
				memcpy(meta->files[i].filehash, (*elems)->string, 20);
				meta->files[i].filehash[21] = '\0';
			}
			else{	// todo: unknown keys
				xx_dbg("----????\n");
				elems++;
			}

			elems++;
		}

		i++;
		plist++;
	}
	meta->files_num = i;

	return 0;
}

// a list of lists of strings
int parse_announce_list(benc *list, xx_metainfo *meta)	//  todo
{
	int				  i		= 0;
	benc			**plist = NULL;
	benc			**elems = NULL;

	if ( NULL == list || NULL == meta) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	plist =  list->b_elems;

	meta->announce_list = NULL;

	while ( *plist ){
		if ( (*plist)->type != BENC_LIST ) {
			xx_err("fuck you...");
			return -1;
		}
		
		elems  = (*plist)->b_elems;
		while ( *elems ){
			if ( (*elems)->type != BENC_STRING ) {
				xx_err("fuck you...");
				return -1;
			}
			if ( NULL == meta->announce_list ) {
				meta->announce_list = (char **)malloc(sizeof(char *) * MAX_ANNOUNCE_NUM);
				if ( !meta->announce_list ) {
					xx_err("malloc failed\n");
					return -1;
				}
				memset(meta->announce_list, 0, sizeof(char *) * MAX_ANNOUNCE_NUM);
			}
			meta->announce_list[i] = (char *)malloc( strlen((*elems)->string) + 1);
			if ( NULL == meta->announce_list[i] ) {
				xx_err("malloc failed\n");
				return -1;
			}
			memcpy(meta->announce_list[i], (*elems)->string, strlen((*elems)->string) + 1);

			i++;
			elems++;
		}

		plist++;
	}

	return 0;
}

int free_meta(xx_metainfo *meta)
{
	int		  i = 0;
//	int		  max = meta->files_num;
	char	**path = NULL;
	char	**announ_list = NULL;

	if ( NULL ==  meta) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	xx_inf("freeing meta........\n");
	if ( meta->announce ) 
		free( meta->announce ); 

	if ( meta->announce_list ){ 
		announ_list = meta->announce_list;
		while ( *announ_list ){
			xx_inf("-----announce: %s\n", *announ_list);
			free(*announ_list);
			announ_list++;
		}
		free(meta->announce_list);
	}


	if ( meta->encoding ) 
		free( meta->encoding ); 

	if ( meta->creat_by )
		free( meta->creat_by );

	if ( meta->comment )
		free( meta->comment );

	if ( meta->creat_by )
		free( meta->creat_by );

	if ( meta->publ )
		free( meta->publ );

	if ( meta->creat_by )
		free( meta->creat_by );

	if ( meta->publ_utf8 )
		free( meta->publ_utf8 );

	if ( meta->publ_url )
		free( meta->publ_url );

	if ( meta->publ_url_utf8 )
		free( meta->publ_url_utf8 );

	if ( meta->pieces )
		free( meta->pieces );

	if ( meta->name )
		free( meta->name );

	if ( meta->name_utf8 )
		free( meta->name_utf8 );

	i = 0;
	xx_dbg("num: %d\n", meta->files_num);
	if ( meta->files ) {
		while ( i < meta->files_num ){
			if ( 0 == meta->info_mode ) {
				if ( meta->files->md5sum ) {
					free(meta->files->md5sum);
				}
				if ( meta->files->ed2k ) {
					free(meta->files->ed2k);
				}
			}else{
				path = meta->files[i].path;
				if ( path ) {
					while ( *path ){
						free(*path);
						path++;
					}
					free(meta->files[i].path);
				}

				path = meta->files[i].path_utf8;
				if ( path ) {
					while ( *path ){
						free(*path);
						path++;
					}
					free(meta->files[i].path_utf8);
				}
			}

			i++;
		}
		free(meta->files);
	}

	return 0;
}

int	show_metainfo(xx_metainfo *meta)
{
	int			  i	   = 0;
//	int			  max  = meta->files_num;
	char		**path = NULL;
	char **announ_list = NULL;

	if ( NULL == meta ) {
		xx_dbg("Input para Invalid\n");
		xx_assert(0);
		return -1;
	}

	xx_inf("\n***************** torrnet info ********************************\n");
	xx_inf("announce:\t %s\n", meta->announce ? meta->announce : "NULL");
	if ( meta->announce_list ){ 
		announ_list = meta->announce_list;
		while ( *announ_list ){
			xx_inf("announce:\t %s\n", *announ_list);
			announ_list++;
		}
	}else
		xx_inf("announce list:\t NULL\n");

	xx_inf("encoding:\t %s\n", meta->encoding ? meta->encoding : "NULL");
	xx_inf("creat_by:\t %s\n", meta->creat_by ? meta->creat_by : "NULL");
	xx_inf("creat_date:\t %d\n", meta->creat_date);
	xx_inf("comment:\t %s\n", meta->comment ? meta->comment : "NULL");
	xx_inf("publ:\t\t %s\n", meta->publ ? meta->publ : "NULL");
	xx_inf("publ_utf8:\t %s\n", meta->publ_utf8 ? meta->publ_utf8 : "NULL");
	xx_inf("publ_url:\t %s\n", meta->publ_url ? meta->publ_url : "NULL");
	xx_inf("publ_url_utf8:\t %s\n", meta->publ_url_utf8 ? meta->publ_url_utf8 : "NULL");

	xx_inf("-------- info dict --------\n");
	xx_inf("info_mode:\t %d\n", meta->info_mode ? 1 : 0);
	xx_inf("piece_len:\t %d\n", meta->piece_len);
//	xx_inf("pieces:\t %s\n", meta->pieces ? meta->pieces : "NULL");
	xx_inf("privat:\t\t %d\n", meta->privat ? 1 : 0);
	xx_inf("name:\t\t %s\n", meta->name ? meta->name : "NULL");
	xx_inf("name_utf8:\t %s\n", meta->name_utf8 ? meta->name_utf8 : "NULL");

	xx_inf("-------- files --------\n");
	xx_inf("files_num:\t %d\n", meta->files_num);
	if ( meta->files ) {
		while ( i < meta->files_num ){

			if ( 0 == meta->info_mode ) {
				xx_inf("length:\t\t %d\n", meta->files->length);
				if ( meta->files->md5sum ) {
					xx_inf("md5sum:\t %s\n", meta->files->md5sum);
				}
				if ( meta->files->ed2k) {
					xx_inf("ed2k:\t %s\n", meta->files->ed2k);
				}
				if ( strlen(meta->files->filehash) > 1) {
					xx_inf("filehash:\t %s\n", meta->files->filehash);
				}
			}
			else{
				xx_inf("length:\t %d\n", meta->files[i].length);
				if ( meta->files[i].md5sum ) 
					xx_inf("md5sum:\t %s\n", meta->files[i].md5sum);

				if ( meta->files[i].ed2k) {
					xx_inf("ed2k:\t %s\n", meta->files[i].ed2k);
				}

				if ( strlen(meta->files[i].filehash) > 1) {
					xx_inf("filehash:\t %s\n", meta->files[i].filehash);
				}

				path = meta->files[i].path;
				if ( path ) {
					while ( *path ){
						xx_inf("path:\t %s\n", *path);
						path++;
					}
				}

				path = meta->files[i].path_utf8;
				if ( path ) {
					while ( *path ){
						xx_inf("path_utf8:\t %s\n", *path);
						path++;
					}
				}
			}

			i++;
			xx_inf("\n");
		}
	}

	return 0;
}
